/**
 * 
 */
package soa.security.mgr.service.impl;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.directory.api.ldap.model.password.PasswordUtil;
import org.apache.directory.api.util.Strings;
import org.apache.poi.ss.usermodel.*;
import org.ldaptive.SearchScope;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.ldap.InvalidAttributeValueException;
import org.springframework.ldap.support.LdapNameBuilder;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import soa.security.Constants;
import soa.security.domain.*;
import soa.security.domain.Account.Accounts;
import soa.security.exception.AdapterException;
import soa.security.exception.AuthenticationException;
import soa.security.exception.ImportException;
import soa.security.exception.OrganizationException;
import soa.security.mgr.service.AccountMgrService;
import soa.security.util.BeanUtils;
import soa.security.util.JaxbUtils;

import java.io.File;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * @author CreGu
 *
 */
@Service
@PropertySource(value = { "classpath:/ldap.properties" })
public class AccountMgrServiceImpl extends BaseMgrServiceImpl implements
		AccountMgrService {
	protected final org.slf4j.Logger log = org.slf4j.LoggerFactory
			.getLogger(this.getClass());

	@Value("#{ environment['import.basepath'] }")
	private String basePath;
	
	@Override
	public List<Applsystem> allApplsystems(String organizationId, String accountId) throws OrganizationException {
		// TODO Auto-generated method stub
		List<Applsystem> list = new ArrayList<Applsystem>();
		try {
			validateOrganization(organizationId);
			//获取账户分组
			OrganizationUnit acUnit = gainOrganizationUnit(organizationId,
					Constants.ACCOUNT);
			//检查账户是否存在
			validateAccount(acUnit.getDn().toString(), accountId);
			//获取该账户下的所有应用系统id
			List<String> pids = accountDao.allApplsystems(accountDao
					.findByPrimaryKey(acUnit.getDn().toString(), accountId));
			//获取应用系统对象集
			for (String string : pids) {
				list.add(applsystemDao.findByDn(string));
			}
		} catch (Exception e) {
			wrapException(e);
		}
		return list;
	}
	
	@Override
	public List<Applsystem> allApplsystems(Account account) throws OrganizationException {
		// TODO Auto-generated method stub
		List<Applsystem> list = new ArrayList<Applsystem>();
		try {
			//获取该账户下的所有应用系统id
			List<String> pids = accountDao.allApplsystems(account);
			//获取应用系统对象集
			for (String string : pids) {
				list.add(applsystemDao.findByDn(string));
			}
		} catch (Exception e) {
			wrapException(e);
		}
		return list;
	}

	@Override
	public void addAccount(String organizationId, Account a)
			throws OrganizationException {
		try {
			validateOrganization(organizationId);
			Organization or = findOrganization(organizationId);
			if (StringUtils.isBlank(a.getUid()))
				a.setUid(a.getBid() + "@"
						+ or.getEname());

			validate(a);

			OrganizationUnit unit = gainOrganizationUnit(organizationId,
					Constants.ACCOUNT);

			a.setDn(a.buildDn(unit.getDn()));
			accountDao.create(a);
		} catch (Exception e) {
			wrapException(e);
		}
	}

	@Override
	public void updateAccount(String organizationId, Account a)
			throws OrganizationException {
		try {
			validateOrganization(organizationId);
			Organization or = findOrganization(organizationId);
			a.setUid(a.getBid() + "@" + or.getEname());
			validate(a);

			OrganizationUnit unit = gainOrganizationUnit(organizationId,
					Constants.ACCOUNT);

			a.setPassword(null);

			a.setDn(a.buildDn(unit.getDn()));
			accountDao.update(a);
		} catch (Exception e) {
			wrapException(e);
		}
	}
	
	@Override
	public void updateLoginAccount(String organizationId, Account a)
			throws OrganizationException {
		try {
			if (StringUtils.isNotBlank(organizationId)) {
				validateOrganization(organizationId);
				Organization or = findOrganization(organizationId);
				a.setUid(a.getBid() + "@" + or.getEname());
				validate(a);

				OrganizationUnit unit = gainOrganizationUnit(organizationId,
						Constants.ACCOUNT);

				a.setPassword(null);

				a.setDn(a.buildDn(unit.getDn()));
			} else {
				a.setUid(a.getBid());
				validate(a);
				a.setPassword(null);
			}
			a.setOldDn(a.getDn().toString());
			accountDao.update(a);
		} catch (Exception e) {
			wrapException(e);
		}
	}

	public static void validate(Account d) {
		Assert.hasText(d.getBid(), "账号编码不能为空");
		Assert.hasText(d.getUid(), "唯一账号不能为空");
		if (StringUtils.isBlank(d.getPassword()))
			d.setPassword(Constants.ACCOUNT_INIT_PWD);

//		Assert.isTrue(Pattern.matches("\\w+", d.getBid()), d.getBid()
//				+ "对应的账号，账号只能输入数字和字母");
//		Assert.isTrue(Pattern.matches("\\w{6,200}", d.getPassword()),
//				d.getBid() + "对应的账号，密码只能输入字符或者数字或者下划线，长度大于6");

		BeanUtils.blank2Null(d);
	}

	@Override
	public List<Account> allAccounts(String organizationId)
			throws OrganizationException {
		return allAccounts(organizationId, null, false);
	}

	@Override
	public List<Account> allAccounts(String organizationId,
			Map<String, Object> parameters, boolean subTree)
			throws OrganizationException {
		List<Account> results = new ArrayList<Account>();
		try {
			validateOrganization(organizationId);

			if (subTree) {
				List<Organization> oneOrg = oneOrg(organizationId);
				for (Organization or : oneOrg) {
					results.addAll(doAllAccounts(or.getBid(), parameters));
				}
			} else {
				results = doAllAccounts(organizationId, parameters);
			}
		} catch (Exception e) {
			wrapException(e);
		}
		return results;
	}
	
	@Override
	public Page<Account> allAccounts(String organizationId, Map<String, Object> parameters, boolean subTree, PageRequest request) throws OrganizationException {
		// TODO Auto-generated method stub
		try {
			//PageRequest request = new PageRequest(pageNo, pageSize);
			validateOrganization(organizationId);
			
			String DN = "";
			if (subTree) {//查询子机构(获取机构DN)
				Organization org = findOrganization(organizationId);
				DN = org.getDn().toString();
			} else {//机构账户组DN
				OrganizationUnit unit = gainOrganizationUnit(organizationId, Constants.ACCOUNT);
				DN = unit.getDn().toString();
			}
			Page<Account> page = accountDao.allAccounts(DN, SearchScope.SUBTREE, parameters, request);
			
			System.out.println(page.getTotalElements());
			System.out.println(page.getTotalPages());
			for (Account a : page.getContent()) {
				a.setPerson(findByUid(a.getPersonBid(), organizationId));
				System.out.println(a.getDn());
			}
			return page;
		} catch (Exception e) {
			// TODO: handle exception
			wrapException(e);
		}
		return null;
	}

	protected List<Account> doAllAccounts(String organizationId,
			Map<String, Object> parameters) {
		List<Account> results = new ArrayList<Account>();

		OrganizationUnit unit = gainOrganizationUnit(organizationId,
				Constants.ACCOUNT);

		results = accountDao.allAccounts(unit.getDn().toString(),
				defaultSearchScope, parameters);

		for (Account a : results) {
			a.setPerson(findByUid(a.getPersonBid(), organizationId));
		}
		sortResult(results);
		return results;
	}

	@Override
	public void pwdReset(String organizationId, String accountId)
			throws OrganizationException {
		pwdUpdate(organizationId, accountId, Constants.ACCOUNT_INIT_PWD);
	}

	@Override
	public void pwdReset(String accountDn) throws OrganizationException {
		try {
			String password = Constants.ACCOUNT_INIT_PWD;

			if (StringUtils.isBlank(password)) {
				throw new OrganizationException("密码不能为空");
			} else {
				if (password.length() < 6)
					throw new OrganizationException("密码长度太短");
			}

			accountDao.pwdUpdate(accountDn, password);
		} catch (InvalidAttributeValueException e) {
			if (!e.getExplanation().contains("invalid reuse of password"))
				throw new OrganizationException(e);
		}
	}

	@Override
	public void pwdUpdate(String organizationId, String accountId,
			String password) throws OrganizationException {
		try {
			Account a = findByUid(accountId);
			String dn = a.getDn().toString();
//			if (!a.isSupered()) {
//				validateOrganization(organizationId);
//
//				OrganizationUnit accountUnit = organizationUnitDao.findBy(
//						findOrganization(organizationId), Constants.ACCOUNT);
//				if (accountUnit == null)
//					throw new IllegalArgumentException("请先初始化机构LDAP");
//
//				validateAccount(accountUnit.getDn().toString(), accountId);
//
//				dn = LdapNameBuilder.newInstance(accountUnit.getDn())
//						.add(Constants.UID, accountId).build().toString();
//			}
			if (StringUtils.isBlank(password)) {
				throw new OrganizationException("密码不能为空");
			} else {
				if (password.length() < 6)
					throw new OrganizationException("密码长度太短");
			}

			accountDao.pwdUpdate(dn, password);
		} catch (InvalidAttributeValueException e) {
			e.printStackTrace();
			if (!e.getExplanation().contains("invalid reuse of password"))
				throw new OrganizationException(e);
		}catch(Exception e){
			wrapException(e);
		}

	}

	@Override
	public void disable(String organizationId, String accountId)
			throws OrganizationException {
		disable(organizationId, accountId, true);
	}

	@Override
	public void disable(String accountDn) throws OrganizationException {
		try {
			accountDao.disable(accountDn, true);
		} catch (Exception e) {
			wrapException(e);
		}
	}

	@Override
	public void enable(String organizationId, String accountId)
			throws OrganizationException {
		disable(organizationId, accountId, false);
	}

	@Override
	public void enable(String accountDn) throws OrganizationException {
		try {
			accountDao.disable(accountDn, false);
		} catch (Exception e) {
			wrapException(e);
		}
	}

	void disable(String organizationId, String accountId, boolean bool)
			throws OrganizationException {
		try {
			validateOrganization(organizationId);
			OrganizationUnit accountUnit = organizationUnitDao.findBy(
					findOrganization(organizationId), Constants.ACCOUNT);
			if (accountUnit == null)
				throw new IllegalArgumentException("请先初始化机构LDAP");

			validateAccount(accountUnit.getDn().toString(), accountId);

			String dn = LdapNameBuilder.newInstance(accountUnit.getDn())
					.add(Constants.UID, accountId).build().toString();
			accountDao.disable(dn, bool);
		} catch (Exception e) {
			wrapException(e);
		}
	}

	@Override
	public Account authenticate(String uid, String password)
			throws OrganizationException {
		try {
			// return accountDao.authenticate(uid, password);
			// 是登录系统，而不是登录ldap服务器
			Account account = accountDao.findByUid(uid);
			// 检验密码
			if (account.isPwdTexted()) {
				// 检验pwdText

			} else {
				// 检验password
			}

			account.getAlgorithmName();

			if (!compareCredentials(account.getDn().toString(), password))
				throw new AuthenticationException("登录失败");

			return account;
		} catch (Exception e) {
			throw new OrganizationException("登录失败", e);
		}
	}

	@Override
	public List<Account> adapt(String xml) throws AdapterException {
		try {
			Accounts bs = JaxbUtils.xml2Object(xml, Accounts.class);
			return bs.getAccount();

		} catch (Exception e) {
			if (log.isDebugEnabled())
				log.debug("格式转换错误", e);
			throw new AdapterException(AdapterException.ERROR_01);
		}
	}

	@Override
	public String unadapt(List<Account> list) throws AdapterException {
		try {
			Accounts bs = new Accounts();
			bs.addAll(list);

			return JaxbUtils.object2Xml(bs, Accounts.class);
		} catch (Exception e) {
			if (log.isDebugEnabled())
				log.debug("格式转换错误", e);
			throw new AdapterException(AdapterException.ERROR_01);
		}
	}

	@Override
	public Account findByDn(String dn) {
		try {
			return accountDao.findByDn(dn);

		} catch (Exception e) {
			printException(e);
			if (log.isDebugEnabled())
				log.debug("错误信息", e);

			return null;
		}
	}

	@Override
	public boolean deleteByDn(String dn) throws OrganizationException {
		try {
			accountDao.deleteByDn(dn);
			return true;
		} catch (Exception e) {
			printException(e);
			if (log.isDebugEnabled())
				log.debug("错误信息", e);

			return false;
		}
	}

	@Override
	public boolean importAccounts(String organizationId, InputStream is)
			throws OrganizationException {
		return importAccounts(organizationId, false, is);
	}

	@Override
	public boolean importAccounts(String organizationId, boolean globaled,
			InputStream is) throws OrganizationException {
		boolean result = true;
		try {
			List<Account> list = readWorkBook(is);
			List<String> msgs = new ArrayList<String>();
			for (Account account : list) {
				organizationId = StringUtils.isBlank(account
						.getOrganizationId()) ? organizationId : account
						.getOrganizationId();

				try {
					addAccount(organizationId, account);
				} catch (Exception e) {
					// 记录错误日志
					String msg = "人员账号：" + account.getBid() + "导入失败，原因："
							+ e.getMessage();
					msgs.add(msg);

					if (result)
						result = false;
				}
			}
			writeLines(msgs);
		} catch (Exception e) {
			wrapException(e);
		}
		return result;
	}

	public File writeLines(List<String> msgs) {
		File file = getError();
		try {
			FileUtils.writeLines(file, "UTF-8", msgs, true);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return file;
	}

	// poi读取excel
	public List<Account> readWorkBook(InputStream inp) throws Exception {
		List<Account> result = new ArrayList<Account>();

		try {
			Workbook wb = WorkbookFactory.create(inp);
			Sheet sheet = wb.getSheetAt(0);

			for (Row row : sheet) {
				// 忽略第一行标题
				if (row.getRowNum() == 0)
					continue;

				int i = 0;
				Iterator<Cell> iter = row.cellIterator();
				while (iter.hasNext()) {
					iter.next();
					i++;
				}

				if (i != 5)
					throw new ImportException("导入文件格式错误");

				result.add(Account.buildFromXls(getString(row.getCell(0)),
						getString(row.getCell(1)), getString(row.getCell(2)),
						getString(row.getCell(3)), getString(row.getCell(4))));
			}
		} catch (Exception e) {
			throw e;
		}

		IOUtils.closeQuietly(inp);
		return result;
	}

	public String getString(Cell cell) throws Exception {
		if (cell == null)
			return null;

		switch (cell.getCellType()) {
		case Cell.CELL_TYPE_STRING:
			return StringUtils.trimToNull(cell.getStringCellValue());
		case Cell.CELL_TYPE_BLANK:
			return null;
		default:
			throw new Exception("请设置excel单元格格式为文本");
		}
	}

	@Override
	public List<Account> findByPersonUid(String organizationId, String personUid)
			throws OrganizationException {
		try {
			validateOrganization(organizationId);

			OrganizationUnit unit = gainOrganizationUnit(organizationId,
					Constants.ACCOUNT);

			return accountDao.allAccounts(unit.getDn().toString(),
					defaultSearchScope, personUid);
		} catch (Exception e) {
			return null;
		}
	}

	@Override
	public void updateAccount(String organizationId, String accountDns,
			String personUid) {
		try {
			validateOrganization(organizationId);

			String[] dns = StringUtils.split(accountDns, ",");
			for (String dn : dns) {
				Account account = accountDao.findByDn(dn);
				account.setPersonBid(personUid);
				accountDao.update(account);
			}

		} catch (Exception e) {
			wrapException(e);
		}
	}

	public Account findByUid(String uid) throws OrganizationException {
		try {
			return accountDao.findByUid(uid);
		} catch (Exception e) {
			throw new OrganizationException("账号[" + uid + "]不存在");
		}
	}

	@Override
	public List<Account> findNonByPersonUid(String organizationId,
			String personUid) throws OrganizationException {
		List<Account> results = new ArrayList<Account>();
		try {
			List<Account> all = allAccounts(organizationId);

			for (Iterator<Account> iterator = all.iterator(); iterator
					.hasNext();) {
				Account account = (Account) iterator.next();
				if (StringUtils.isNotBlank(account.getPersonBid()))
					iterator.remove();
			}

			results.addAll(all);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return results;
	}

	@Override
	public boolean compareCredentials(String dn, String password) {
		try {
			Account account = findByDn(dn);
			Account a = findByUid(account.getUid());

			byte[] storedPasswords = a.getPasswordByte();
			byte[] credentials = Strings.getBytesUtf8(password);

			return PasswordUtil
					.compareCredentials(credentials, storedPasswords);
		} catch (Exception e) {
			wrapException(e);
		}
		return false;
	}

	@Override
	public List<Account> findByBase(String baseDn) {
		List<Account> result = new ArrayList<Account>();
		try {

			return accountDao.allAccounts(baseDn, defaultSearchScope,
					(Map<String, Object>) null);
		} catch (Exception e) {
			printException(e);
		}
		return result;
	}

	@Override
	public Account systemLogin(String bid, String orginationDN, String password)
			throws OrganizationException {
		try {
			if (StringUtils.isBlank(orginationDN) || "-1".equals(orginationDN))
				return authenticate(bid, password);

			Organization organization = organizationDao.findByDn(orginationDN);
			return authenticate(bid + "@" + organization.getEname(), password);
		} catch (Exception e) {
			wrapException(e);
		}
		return null;
	}

	@Override
	public void initializeSuperAccount() {
		try {
			Account account = Account.buildSuper();
			if (!existAccount(account.getUid())) {
				account.setDn(account.buildDn(Region.root().buildDn()));
				accountDao.create(account);
			}

		} catch (Exception e) {
			printException(e);
		}
	}

	public Person findByUid(String uid, String organizationId) {
		Person result = new Person();
		try {
			validateOrganization(organizationId);

			OrganizationUnit unit = gainOrganizationUnit(organizationId,
					Constants.PERSON);

			return personDao.findByPrimaryKey(unit.getDn().toString(), uid);
		} catch (Exception e) {
			printException(e);
		}
		return result;
	}

	@Override
	public Account existAccount(String organizationId, Account a) {
		try {
			Organization or = findOrganization(organizationId);
			if (StringUtils.isBlank(a.getUid()))
				a.setUid(a.getBid() + "@"
						+ or.getEname());
			return findByUid(a.getUid());
		} catch (Exception e) {
			log.debug("账号不存在");
		}
		return null;
	}

	public boolean existAccount(String uid) {
		try {
			findByUid(uid);
			return true;
		} catch (Exception e) {
			return false;
		}
	}

	@Override
	public void remove(String dn) {
		String[] dns = StringUtils.split(dn, ";");
		if (dns == null)
			return;

		for (String string : dns) {
			ldapTemplate.unbind(string);
		}
	}

	@Override
	public File getTemplate() {
		String exportPath = System.getProperty("user.dir");
		String path = "/" + basePath + "/importTemplate.xlsx";

		File file = new File(exportPath, path);
		return file;
	}

	@Override
	public File getError() {
		String exportPath = FileUtils.getTempDirectoryPath();
		String path = "/" + basePath + "/error.txt";

		File file = new File(exportPath, path);
		return file;
	}

	@Override
	public void bind(String organizationId, String accountId, String personId) {
		try {
			validateOrganization(organizationId);
			Assert.hasText(accountId, accountId + "不能为空");
			Assert.hasText(personId, personId + "不能为空");

			OrganizationUnit unit = gainOrganizationUnit(organizationId,
					Constants.ACCOUNT);
			String[] as = StringUtils.split(accountId, ",");
			for (String string : as) {
				Account a = new Account();
				a.setUid(string);
				accountDao.bind(a.buildDn(unit.getDn()).toString(), personId);
			}
		} catch (Exception e) {
			wrapException(e);
		}
	}
	
	@Override
	public boolean addApplsystem(String organizationId, String accountId, String applsystemIds)
			throws OrganizationException {
		// TODO Auto-generated method stub
		try {
			validateOrganization(organizationId);

			OrganizationUnit acUnit = gainOrganizationUnit(organizationId, Constants.ACCOUNT);
			validateAccount(acUnit.getDn().toString(), accountId);
			
			Account account = accountDao.findByPrimaryKey(acUnit.getDn().toString(), accountId);
			String[] pids = StringUtils.split(applsystemIds, ",");
			for (String pid : pids) {
				OrganizationUnit unit = gainOrganizationUnit(organizationId, Constants.APPLSYSTEM);
				validateApplsystem(unit.getDn().toString(), pid);
				accountDao.addApplsystems(account, applsystemDao.findByPrimaryKey(unit.getDn().toString(), pid));
			}
			return true;
		} catch (Exception e) {
			wrapException(e);
		}
		return false;
	}
	
}
